﻿/*
 * SystemRuntime
 * 
 * SystemRuntime.CreatePluginInstance()
 * 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Primitives;
using System.ComponentModel.Composition.Hosting;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using CompositionPlugin.Service;
using CompositionPlugin.Primitives;

namespace CompositionPlugin
{
    public class SystemRuntime : Runtime, IDisposable
    {
        #region static
        /// <summary>
        /// Current system runtime
        /// </summary>
        public static SystemRuntime Current { get; private set; }
        #endregion

        public class Module
        {
            protected Module(ModuleDefinition definition, ModuleRuntime runtime, Type moduleType, object instance)
            {
                if (definition == null)
                    throw new ArgumentNullException("definition");
                if (runtime == null)
                    throw new ArgumentNullException("runtime");
                if (moduleType == null)
                    throw new ArgumentNullException("moduleType");
                if (instance == null)
                    throw new ArgumentNullException("instance");
                // init
                this.Definition = definition;
                this.ModuleType = moduleType;
                this.Runtime = runtime;
                this.Instance = instance;
            }

            public ModuleDefinition Definition { get; private set; }

            public ModuleRuntime Runtime { get; private set; }

            public Type ModuleType { get; private set; }

            public object Instance { get; private set; }
        }
        /// <summary>
        /// Module
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class Module<T> : Module where T : class
        {
            #region static
            /// <summary>
            /// Current Module runtime
            /// </summary>
            public static Module<T> Current
            {
                get
                {
                    if (!SystemRuntime._moduleDict.ContainsKey(typeof(T)))
                    {
                        // for lock(this)
                        lock (SystemRuntime.Current)
                        {
                            if (!SystemRuntime._moduleDict.ContainsKey(typeof(T)))
                            {
                                // create new module
                                return Load();
                            }
                            else
                            {
                                return SystemRuntime._moduleDict[typeof(T)] as Module<T>;
                            }
                        }
                    }
                    else
                    {
                        return SystemRuntime._moduleDict[typeof(T)] as Module<T>;
                    }
                }
            }

            public static Module<T> Load()
            {
                try
                {
                    return SystemRuntime.Current.CreateModule<T>();
                }
                catch (Exception error)
                {
                    SystemRuntime.Current.Log.Write(TraceEventType.Error, 0, "Cannot load module " + typeof(T).FullName + " for error " + error.ToString());
                }

                return null;
            }

            #endregion
            
            public new T Instance { get; private set; }

            #region internal

            internal Module(ModuleDefinition definition, ModuleRuntime runtime, T instance)
                : base(definition, runtime, typeof(T), instance)
            {
                if (instance == null)
                    throw new ArgumentNullException("instance");
                this.Instance = instance;
            }

            #endregion
        }

        public SystemRuntime(IComposition composition) 
            : this(new Tuple<Type, IRuntimeService>[] { new Tuple<Type, IRuntimeService>(typeof(IComposition), composition) }) { }

        public SystemRuntime(ExportProvider exportProvider, IEnumerable<Tuple<Type, IRuntimeService>> services)
            : this(services.Concat(new Tuple<Type, IRuntimeService>[] { new Tuple<Type, IRuntimeService>(typeof(IComposition), new CompositionService(exportProvider)) })) { }

        public SystemRuntime(IEnumerable<Tuple<Type, IRuntimeService>> services)
            : base(services)
        {
            if (SystemRuntime.Current != null)
                throw new InvalidOperationException("System runtime has already created");
            if (this.GetService<IComposition>() == null)
                throw new ArgumentException("Miss Composition Service");
            // current
            SystemRuntime.Current = this;
            // check all necessary services, if not exist then use default one
            foreach (var service in _necessaryServices)
                if (this.GetService(service.Key) == null)
                    // create
                    this.SetService(service.Key, service.Value());
        }

        public virtual void Run()
        {
            if (Interlocked.CompareExchange(ref _running, 1, 0) == 0)
            {
                // init all
                this.InitializeAllService();
                // load
                this.LoadLifetimeModules();
            }
            else
            {
                throw new InvalidOperationException("SystemRuntime is running");
            }
        }

        public bool LoadModule<T>() where T : class
        {
            return SystemRuntime.Module<T>.Load() != null;
        }

        public bool LoadModule(string moduleName)
        {
            if (string.IsNullOrWhiteSpace(moduleName))
                throw new ArgumentException("Invalid moduleName");
            try
            {
                // search
                var module = this.ModuleRepository.GetModuleDefinition(moduleName);
                if (module == null)
                {
                    SystemRuntime.Current.Log.Write(TraceEventType.Warning, 0, "SystemRuntime: LoadModule, Module:" + moduleName + " not found");
                    return false;
                }
                // make generic type
                var moduleType = typeof(Module<>).MakeGenericType(module.Metadata.ModuleType);
                var method = moduleType.GetMethod("Load", BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod);
                var r = method.Invoke(null, null);
                return r != null;
            }
            catch (Exception error)
            {
                SystemRuntime.Current.Log.Write(TraceEventType.Error, 0, "SystemRuntime: LoadModule error :" + error.ToString());
            }

            return false;
        }

        public void Dispose()
        {
            // dispose runtime services
            foreach (var service in this.RuntimeServices)
                service.OnRuntimeDisposing();
        }

        #region Service
        /// <summary>
        /// Composition
        /// </summary>
        public IComposition Composition
        {
            get
            {
                return this.GetService<IComposition>();
            }
        }
        /// <summary>
        /// Imported plugin manager
        /// </summary>
        public IImportedPluginManager ImportedPluginManager
        {
            get
            {
                return this.GetService<IImportedPluginManager>();
            }
        }
        /// <summary>
        /// Plugin repository
        /// </summary>
        public IModuleRepository ModuleRepository
        {
            get
            {
                return this.GetService<IModuleRepository>();
            }
        }
        /// <summary>
        /// Plugin activator
        /// </summary>
        public IModuleActivator ModuleActivator
        {
            get
            {
                return this.GetService<IModuleActivator>();
            }
        }
        /// <summary>
        /// Plugin monitor
        /// </summary>
        public IModuleMonitor ModuleMonitor
        {
            get
            {
                return this.GetService<IModuleMonitor>();
            }
        }
        /// <summary>
        /// Log
        /// </summary>
        public ILog Log
        {
            get
            {
                return this.GetService<ILog>();
            }
        }
        /// <summary>
        /// Configuration
        /// </summary>
        public IConfiguration Configuration
        {
            get
            {
                return this.GetService<IConfiguration>();
            }
        }

        protected PriorityTaskDispatcher TaskDispatcher
        {
            get
            {
                return this.GetService<PriorityTaskDispatcher>();
            }
        }

        #endregion

        #region internal


        #endregion

        #region protected

        /// <summary>
        /// Try to get service from composition container
        /// </summary>
        /// <param name="serviceContractType"></param>
        /// <returns></returns>
        protected override object FailedToRetrieveService(Type serviceContractType)
        {
            IEnumerable<Export> exports;

            if (this.Composition.Container.TryGetExports(new ContractBasedImportDefinition(serviceContractType.FullName, serviceContractType.Name, null, ImportCardinality.ExactlyOne, false, true, CreationPolicy.Any), new AtomicComposition(), out exports))
            {
                return exports.First().Value;
            }
            else
            {
                return null;
            }
        }
        /// <summary>
        /// Create new module
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        protected virtual Module<T> CreateModule<T>() where T : class
        {
            lock (this)
            {
                if (SystemRuntime._moduleDict.ContainsKey(typeof(T)))
                    return SystemRuntime._moduleDict[typeof(T)] as Module<T>;
                // create new module
                var definition = this.ModuleRepository.GetModuleDefinition<T>();
                if (definition == null)
                    throw new InvalidOperationException(string.Format("Cannot find module with type: {0}", typeof(T).Name));
                // create new module runtime service
                var services = this.RuntimeServices
                    .Where(p => p is IModuleRuntimeServiceGenerator)
                    .Select(p =>
                    {
                        Type contractType = null;
                        var service = (p as IModuleRuntimeServiceGenerator).GetModuleRuntimeService(definition, out contractType);
                        return new Tuple<Type, IRuntimeService>(contractType, service);
                    });
                // create module runtime
                var moduleRuntime = this.CreateModuleRuntime(services);
                // create module instance
                var instance = this.ModuleActivator.Active(definition) as T;
                if(instance == null)
                    throw new InvalidOperationException("Active module failed");
                // create module
                Module<T> module = new Module<T>(definition, moduleRuntime, instance);
                // add to dict
                SystemRuntime._moduleDict[typeof(T)] = module;
                // notify service
                this.OnModuleCreated<T>(module);
                // return
                return module;
            }
        }
        /// <summary>
        /// Destory a module
        /// </summary>
        /// <typeparam name="T"></typeparam>
        protected virtual void DestroyModule<T>() where T : class
        {
            // destroy plugin instance
            var module = SystemRuntime.Module<T>.Current;
            // notify service
            this.OnModuleDestroy<T>(module);
            // deactive
            this.ModuleActivator.Deactive(module.Definition, module.Instance);
            // destroy runtime
            this.DestroyModuleRuntime(module.Runtime);
        }
        /// <summary>
        /// Create module runtime
        /// </summary>
        /// <param name="services"></param>
        /// <returns></returns>
        protected virtual ModuleRuntime CreateModuleRuntime(IEnumerable<Tuple<Type, IRuntimeService>> services)
        {
            var moduleRuntime = new ModuleRuntime(services);
            // init
            moduleRuntime.Initialize();

            return moduleRuntime;
        }
        /// <summary>
        /// Destroy module runtime
        /// </summary>
        /// <param name="runtime"></param>
        protected virtual void DestroyModuleRuntime(ModuleRuntime runtime)
        {
            // destory plugin runtime
            runtime.Destroy();
        }
        /// <summary>
        /// On module created
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="module"></param>
        protected virtual void OnModuleCreated<T>(Module<T> module) where T : class
        {
            foreach (IModuleEffective service in this.RuntimeServices.Where(p => p is IModuleEffective))
                service.OnModuleActiving<T>(module);
        }
        /// <summary>
        /// On module destroy
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="module"></param>
        protected virtual void OnModuleDestroy<T>(Module<T> module) where T : class
        {
            foreach (IModuleEffective service in this.RuntimeServices.Where(p => p is IModuleEffective))
                service.OnModuleDestroying<T>(module);
        }

        #endregion

        #region private

        /// <summary>
        /// The type of value is SystemRuntime.Module with generic type Key
        /// </summary>
        private readonly static Dictionary<Type, object> _moduleDict = new Dictionary<Type, object>();
        /// <summary>
        /// is runtime running
        /// </summary>
        private static int _running = 0;

        private readonly static Dictionary<Type, Func<IRuntimeService>> _necessaryServices = new Dictionary<Type, Func<IRuntimeService>>()
        {
            // config
            {typeof(IConfiguration), new Func<IConfiguration>(()=>ConfigurationManager.GetSection("compositionPlugin") as IConfiguration)},
            {typeof(IImportedPluginManager), new Func<IImportedPluginManager>(()=> new ConfigurableImportPluginManager())},
            // log
            {typeof(ILog), new Func<ILog>(()=>new Log())},
            // others
            {typeof(IModuleRepository), new Func<IModuleRepository>(()=>new ModuleRepository())},
            {typeof(IModuleActivator), new Func<IModuleActivator>(()=> new ModuleActivator())},
            {typeof(PriorityTaskDispatcher), new Func<PriorityTaskDispatcher>(()=> new PriorityTaskDispatcher())}
        };

        private void LoadLifetimeModules()
        {
            foreach (var moduleDef in this.ModuleRepository.Modules)
                if (moduleDef.Metadata.ActiveType == ModuleActiveType.LifetimeService)
                    this.ModuleActivator.Active(moduleDef);
        }
        #endregion
    }
}
